package com.ydl.shopping.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ydl.shopping.async.AsyncTaskService;
import com.ydl.shopping.core.dto.LoginDTO;
import com.ydl.shopping.core.dto.RegisterDTO;
import com.ydl.shopping.core.dto.UserDTO;
import com.ydl.shopping.core.entity.LoginUser;
import com.ydl.shopping.core.enums.ResultEnum;
import com.ydl.shopping.core.vo.ResultVO;
import com.ydl.shopping.domain.TbLoginInfo;
import com.ydl.shopping.domain.TbRole;
import com.ydl.shopping.domain.TbUser;
import com.ydl.shopping.domain.TbUserRole;
import com.ydl.shopping.exception.ServiceException;
import com.ydl.shopping.mapper.TbUserMapper;
import com.ydl.shopping.security.context.LoginUserContextHolder;
import com.ydl.shopping.service.ITbLoginInfoService;
import com.ydl.shopping.service.ITbRoleService;
import com.ydl.shopping.service.ITbUserRoleService;
import com.ydl.shopping.service.ITbUserService;
import com.ydl.shopping.utils.CacheUtil;
import com.ydl.shopping.utils.SecurityUtil;
import com.ydl.shopping.utils.ip.IpUtil;
import com.ydl.shopping.utils.web.TokenService;
import com.ydl.shopping.utils.web.WebUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

import static com.ydl.shopping.constant.RedisConstants.*;
import static com.ydl.shopping.constant.SystemConstants.UNKNOWN;

@Slf4j
@Service
public class TbUserServiceImpl extends ServiceImpl<TbUserMapper, TbUser> implements ITbUserService {
    @Resource
    private CacheUtil cacheUtil;

    @Resource
    private TbUserMapper tbUserMapper;

    @Resource
    private ITbRoleService tbRoleService;

    @Resource
    private ITbUserRoleService tbUserRoleService;

    @Resource
    private TokenService tokenService;

    @Resource
    private ITbLoginInfoService tbLoginInfoService;

    @Resource
    private AsyncTaskService asyncTaskService;

    @Resource
    private AuthenticationManager authenticationManager;

    @Value(value = "${user.password.maxRetryCount}")
    private long maxRetryCount;

    @Value(value = "${user.password.lockTime}")
    private long lockTime;

    @Value(value = "${user.password.defaultPassword}")
    private String defaultPassword;

    @Override
    public QueryWrapper<TbUser> queryWrapper(TbUser entity) {
        QueryWrapper<TbUser> queryWrapper = new QueryWrapper<>();
        String username = entity.getUsername();
        if (StrUtil.isNotBlank(username)) {
            queryWrapper.like("username", username);
        }
        String tel = entity.getTel();
        if (StrUtil.isNotBlank(tel)) {
            queryWrapper.like("tel", tel);
        }
        String email = entity.getEmail();
        if (StrUtil.isNotBlank(email)) {
            queryWrapper.like("email", email);
        }
        String nickname = entity.getNickname();
        if (StrUtil.isNotBlank(nickname)) {
            queryWrapper.like("nickname", nickname);
        }
        Integer gender = entity.getGender();
        if (gender != null) {
            queryWrapper.eq("gender", gender);
        }
        Integer status = entity.getStatus();
        if (status != null) {
            queryWrapper.eq("status", status);
        }

        return queryWrapper;
    }

    @Override
    public List<TbUser> selectList(TbUser tbUser) {
        return tbUserMapper.queryList(tbUser);
    }

    @Override
    public String saveUser(TbUser tbUser) {
        if (checkUsernameExist(tbUser.getUsername())) {
            return "用户名已被注册";
        }

        String password = tbUser.getPassword();
        if (StrUtil.isBlank(password)) {
            password = defaultPassword;
        }
        String nickname = tbUser.getNickname();
        if (StrUtil.isBlank(nickname)) {
            tbUser.setNickname(String.format("user-%s", IdUtil.fastSimpleUUID()));
        }
        String icon = tbUser.getIcon();
        if (StrUtil.isBlank(icon)) {
            tbUser.setIcon(UNKNOWN);
        }
        String encryptPassword = SecurityUtil.encryptPassword(password);
        tbUser.setPassword(encryptPassword);

        // 保险措施，防止id重复
        tbUser.setId(null);
        boolean saveResult = save(tbUser);

        if (!saveResult) {
            throw new ServiceException(ResultEnum.ERROR);
        }
        // 获取新增用户ID
        Long userId = tbUser.getId();
        // 获取用户角色
        List<Long> roleIds = tbUser.getRoleIds();
        if (roleIds != null && !roleIds.isEmpty()) {
            tbUserRoleService.saveUserRole(userId, roleIds);
        }

        return null;
    }

    @Override
    public boolean updateUser(TbUser tbUser) {
        // 1. 判断是否重复用户名
//        if (checkUsernameExist(tbUser.getUsername())) {
//            throw new ServiceException("用户名已存在");
//        }

        // 2. 获取密码
        String password = tbUser.getPassword();
        // 2.1 判断密码是否不为空
        if (StrUtil.isNotBlank(password)) {
            String encrypted = SecurityUtil.encryptPassword(password);
            tbUser.setPassword(encrypted);
        }

        // 3. 修改用户信息
        return updateById(tbUser);
    }

    @Override
    public boolean checkUsernameExist(String username) {
        return query().eq("username", username).count() > 0;
    }

    @Override
    public ResultVO register(RegisterDTO registerDTO) {
        String username = registerDTO.getUsername();
        if (checkUsernameExist(username)) {
            return ResultVO.error("用户名已被注册");
        }

        TbRole defaultRole;

        String rolePerms = registerDTO.getRolePerms();
        if (StrUtil.isNotBlank(rolePerms)) {
            // 根据角色标识获取角色
            defaultRole = tbRoleService.getRoleByPerms(rolePerms);
            if (defaultRole == null) {
                return ResultVO.error("角色不存在");
            }
        } else {
            // 获取默认角色
            defaultRole = tbRoleService.getDefaultRole();
            if (defaultRole == null) {
                return ResultVO.error("默认角色未设置");
            }
        }

        TbUser registerUser = new TbUser();
        registerUser.setUsername(username);
        registerUser.setPassword(registerDTO.getPassword());
        // 默认用户昵称
        registerUser.setNickname(String.format("my-user-%s", StrUtil.uuid()));
        // 默认用户头像
        registerUser.setIcon(UNKNOWN);

        registerUser.setCreateBy(username);
        boolean registerResult = save(registerUser);
        if (!registerResult) {
            throw new RuntimeException("注册失败");
        }
        // 获取注册用户ID
        Long userId = registerUser.getId();
        // 设置用户角色
        boolean setRoleResult = tbUserRoleService.save(new TbUserRole(userId, defaultRole.getId()));
        if (!setRoleResult) {
            throw new RuntimeException("注册失败");
        }

        return ResultVO.success("注册成功");
    }

    @Override
    public ResultVO login(LoginDTO loginDTO) {
        HttpServletRequest request = WebUtil.getRequest();
        assert request != null;

        // 获取用户名
        String username = loginDTO.getUsername();
        // 获取密码
        String password = loginDTO.getPassword();
        // 获取登录类型
        Integer loginType = loginDTO.getLoginType();

        // 判断用户名是否为空
        if (StrUtil.isBlank(username)) {
            return ResultVO.error("用户名不能为空");
        }
        // 判断密码是否为空
        if (StrUtil.isBlank(password)) {
            return ResultVO.error("密码不能为空");
        }
        // 交由spring security进行验证
        UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username, password);
        Authentication authenticate = authenticationManager.authenticate(authenticationToken);

        LoginUser loginUser = (LoginUser) authenticate.getPrincipal();
        if (Objects.isNull(loginUser)) {
            return ResultVO.error("用户名或密码错误");
        }
        TbLoginInfo tbLoginInfo = loginUser.getTbLoginInfo();
        // 获取用户信息
        TbUser user = loginUser.getTbUser();

        // 获取用户类型
        Integer type = user.getType();
        if (type.equals(1) && loginType.equals(0)) {
            return ResultVO.error("用户名不存在");
        }

        Long userId = user.getId();

        tbLoginInfo.setStatus(1);

        // 生成登录令牌
        String token = createToken(user);
        tbLoginInfo.setMsg("登录成功");
        // myAsync.execute(new SaveLoginInfoThread(sysLoginInfo, sysLoginInfoService));
        asyncTaskService.execute(p -> tbLoginInfoService.save(p), tbLoginInfo, "新增登录访问");

        loginUser.setTbLoginInfo(tbLoginInfo);
        loginUser.setToken(token);

        // 获取用户权限信息
        String securityLoginKey = getSecurityLoginKey(userId, token);
        // 删除Redis中的权限信息
        cacheUtil.delete(securityLoginKey);

        LoginUserContextHolder.clearContext();

        // 将系统用户登录信息存入Redis
        cacheUtil.setStrJson(securityLoginKey, loginUser, LOGIN_KEY_TTL, TimeUnit.MINUTES);

        log.info("[登录模块] 请求地址'{}'登录成功 ========= ip：{}", request.getRequestURI(), IpUtil.getIpAddress(request));
        return ResultVO.success(token, "登录成功");
    }

    @Override
    public UserDTO me() {
        LoginUser loginUser = SecurityUtil.getLoginUser();
        TbUser tbUser = loginUser.getTbUser();
        if (StrUtil.isBlank(tbUser.getIcon())) {
            tbUser.setIcon(UNKNOWN);
        }
        TbLoginInfo tbLoginInfo = loginUser.getTbLoginInfo();
        UserDTO userDTO = BeanUtil.toBean(tbUser, UserDTO.class);
        userDTO.setType(tbUser.getType());
        userDTO.setLoginLocation(tbLoginInfo.getLoginLocation());
        userDTO.setIp(tbLoginInfo.getIp());
        userDTO.setLoginTime(tbLoginInfo.getLoginTime());
        userDTO.setOs(tbLoginInfo.getOs());
        userDTO.setRemark(tbUser.getRemark());

        return userDTO;
    }

    @Override
    public ResultVO logOut() {
        HttpServletRequest request = WebUtil.getRequest();

        // 获取系统用户相关信息
        LoginUser loginUser = SecurityUtil.getLoginUser();
        Long userId = loginUser.getUserId();
        TbLoginInfo tbLoginInfo = loginUser.getTbLoginInfo();
        tbLoginInfo.setId(null);
        tbLoginInfo.setLoginTime(new Date());
        String message = "退出登录成功";
        tbLoginInfo.setMsg(message);
        // myAsync.execute(new SaveLoginInfoThread(sysLoginInfo, sysLoginInfoService));
        asyncTaskService.execute(p -> tbLoginInfoService.save(p), tbLoginInfo, "新增登录访问");

        String token = loginUser.getToken();

        // 获取用户权限信息
        String securityLoginKey = getSecurityLoginKey(userId, token);
        // 删除Redis中的权限信息
        cacheUtil.delete(securityLoginKey);

        LoginUserContextHolder.clearContext();
        log.info("[登录模块] 请求地址'{}'退出登录成功 ========= ip：{}", request.getRequestURI(), IpUtil.getIpAddress(request));
        return ResultVO.success(message);
    }

    /** 获取安全认证登录Key */
    private String getSecurityLoginKey(Long userId, String token) {
        return LOGIN_KEY + userId + ":" + token;
    }

    /**
     * 保存用户信息并返回登录令牌
     *
     * @param tbUser 保存的用户
     */
    private String createToken(TbUser tbUser) {
        Long userId = tbUser.getId();
        // 生成登录令牌
        String token = tokenService.generateToken(userId);
        // 清除登录密码错误记录
        clearLoginError(tbUser.getUsername());

        return token;
    }

    /**
     * 清除登录密码错误记录
     *
     * @param username 用户名
     */
    private void clearLoginError(String username) {
        String key = getLoginPwdError(username);
        cacheUtil.delete(key);
    }

    @Override
    public void checkLoginError(String username) {
        String key = getLoginPwdError(username);
        String count = cacheUtil.getStr(key);
        if (StrUtil.isNotBlank(count) && Long.parseLong(count) > maxRetryCount) {
            // return MessageUtil.message("user.password.retry.limit.exceed", maxRetryCount, lockTime);
            throw new ServiceException("密码输入错误"+maxRetryCount+"次，帐户锁定"+lockTime+"分钟");
        }
    }

    @Override
    public Long loginErrorRecord(String username) {
        String key = getLoginPwdError(username);
        String count = cacheUtil.getStr(key);
        if (StrUtil.isBlank(count)) {
            // 有效期内错误密码登录开始记录
            cacheUtil.setStrJson(key, "1", lockTime, TimeUnit.MINUTES);
            return 1L;
        }
        Long errorRecord = cacheUtil.incrementStr(key);
        if (errorRecord > maxRetryCount) {
            // 重置记录有效期，账户锁定

            cacheUtil.expire(key, lockTime, TimeUnit.MINUTES);
        }
        return errorRecord;
    }

    /** 登录密码错误记录 */
    private String getLoginPwdError(String username) {
        return LOGIN_PASSWORD_ERROR + username;
    }
}
